#include "grep.h"

#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

int main(int argc, char** argv) {
  if (argc < 2) {
    printf("No input\n");
    return 1;
  }
  // read flags
  struct flags flags = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  int arg_pointer = 1;
  char* regpointer = NULL;
  while (argv[arg_pointer][0] == '-' && arg_pointer < argc) {
    int pointer = 1;
    int end_now = 0;
    while (argv[arg_pointer][pointer] && !end_now) {
      switch (argv[arg_pointer][pointer]) {
        case 'e':
          flags.pattern = 1;
          if (!flags.regex_from_file && arg_pointer + 1 < argc) {
            regpointer = argv[arg_pointer + 1];
          }
          arg_pointer += 1;
          end_now = 1;
          break;
        case 'i':
          flags.ignore_case = 1;
          break;
        case 'v':
          flags.invert = 1;
          break;
        case 'c':
          flags.only_count_of_lines = 1;
          break;
        case 'l':
          flags.only_matching_file_names = 1;
          break;
        case 'n':
          flags.number = 1;
          break;
        case 'h':
          flags.no_file_names = 1;
          break;
        case 's':
          flags.suppress_error = 1;
          break;
        case 'f':
          if (!flags.regex_from_file && arg_pointer + 1 < argc) {
            regpointer = argv[arg_pointer + 1];
          }
          flags.regex_from_file = 1;
          arg_pointer += 1;
          end_now = 1;
          break;
        case 'o':
          flags.output_matching = 1;
          break;
        default:
          printf("Incorrect flag(s)\n");
          return 1;
      }
      pointer += 1;
    }
    arg_pointer += 1;
  }
  // reading pattern (if not added before)
  if (!regpointer && arg_pointer >= argc) {
    printf("Not enough arguments\n");
    return 1;
  } else if (!regpointer && arg_pointer < argc) {
    regpointer = argv[arg_pointer];
    arg_pointer++;
  } else if (flags.regex_from_file) {
    FILE* regex_file = fopen(regpointer, "r");
    if (regex_file) {
      regpointer = read_line(regex_file, 1);
      fclose(regex_file);
    } else {
      printf("No such file to read regex\n");
      return 1;
    }
  }

  // compiling regex
  regex_t preq;
  int regflags = REG_EXTENDED;
  if (flags.ignore_case) {
    regflags = regflags | REG_ICASE;
  }
  if (regcomp(&preq, regpointer, regflags)) {
    if (flags.regex_from_file) {
      free(regpointer);
    }
    regfree(&preq);
    printf("Error, when compiling regex\n");
    return 1;
  }

  // find file
  if (arg_pointer + 1 >= argc) {
    flags.no_file_names = 1;
  }

  while (arg_pointer < argc) {
    FILE* file = fopen(argv[arg_pointer], "r");
    if (file) {
      grep(file, argv[arg_pointer], flags, preq);
      fclose(file);
    } else if (!flags.suppress_error) {
      printf("No such file or directory\n");
    }
    arg_pointer += 1;
  }

  // freeing resources
  regfree(&preq);
  if (flags.regex_from_file) {
    free(regpointer);
  }
  return 0;
}

void grep(FILE* file, char* filename, struct flags flags, regex_t preq) {
  char* line;
  int cont_flag = 1;
  int line_counter = 1;
  while ((line = read_line(file, 0)) != NULL && cont_flag) {
    // searching for matches
    regmatch_t pmatch;
    char* line_ptr = line;
    int result = regexec(&preq, line_ptr, 1, &pmatch, 0);

    if (flags.only_count_of_lines) {
      line_counter += (!result) ^ flags.invert;
    } else if (flags.only_matching_file_names && (!result) ^ flags.invert) {
      printf("%s\n", filename);
      cont_flag = 0;
    } else {
      while (line_ptr && (!result) ^ flags.invert) {
        if (!flags.no_file_names) {
          printf("%s:", filename);
        }
        if (flags.number) {
          printf("%d:", line_counter);
        }
        if (flags.output_matching) {
          for (int ptr = pmatch.rm_so; ptr < pmatch.rm_eo; ptr++) {
            putchar(line_ptr[ptr]);
          }
          putchar('\n');
          line_ptr = line_ptr + pmatch.rm_eo;
          result = regexec(&preq, line_ptr, 1, &pmatch, 0);
        } else {
          printf("%s", line);
          if (line[strlen(line) - 1] != '\n') {
            printf("\n");
          }
          line_ptr = NULL;
        }
      }
      line_counter += 1;
    }

    free(line);
    line = NULL;
  }
  if (line != NULL) {
    free(line);
  }
  if (flags.only_count_of_lines) {
    if (!flags.no_file_names) {
      printf("%s:", filename);
    }
    printf("%d\n", line_counter - 1);
  }
}

/*
Reads file until meets new line or EOF.
Dynamically allocates memory (mult size by 2 when reaching limit)*/
char* read_line(FILE* file, int ignore_new_line) {
  int len = 8;
  int pointer = 0;
  char* result = malloc((len + 2) * sizeof(char));
  char curchar = getc(file);
  if (curchar == EOF) {
    free(result);
    result = NULL;
  } else {
    while (curchar != '\n' && curchar != EOF) {
      if (pointer + 1 >= len) {
        len += 10;
        char* tmp = NULL;
        while (tmp == NULL) {
          tmp = realloc(result, (len + 2) * sizeof(char));
        }
        result = tmp;
      }
      result[pointer] = curchar;
      pointer++;
      curchar = getc(file);
    }
    if (curchar == '\n' && !ignore_new_line) {
      result[pointer] = '\n';
      pointer++;
    }
    result[pointer] = '\0';
  }

  return result;
}